home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / DAVCMDS.PY < prev    next >
Encoding:
Python Source  |  2000-05-26  |  14.4 KB  |  357 lines

  1. ##############################################################################
  2. # Zope Public License (ZPL) Version 1.0
  3. # -------------------------------------
  4. # Copyright (c) Digital Creations.  All rights reserved.
  5. # This license has been certified as Open Source(tm).
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. # 1. Redistributions in source code must retain the above copyright
  10. #    notice, this list of conditions, and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. #    notice, this list of conditions, and the following disclaimer in
  13. #    the documentation and/or other materials provided with the
  14. #    distribution.
  15. # 3. Digital Creations requests that attribution be given to Zope
  16. #    in any manner possible. Zope includes a "Powered by Zope"
  17. #    button that is installed by default. While it is not a license
  18. #    violation to remove this button, it is requested that the
  19. #    attribution remain. A significant investment has been put
  20. #    into Zope, and this effort will continue if the Zope community
  21. #    continues to grow. This is one way to assure that growth.
  22. # 4. All advertising materials and documentation mentioning
  23. #    features derived from or use of this software must display
  24. #    the following acknowledgement:
  25. #      "This product includes software developed by Digital Creations
  26. #      for use in the Z Object Publishing Environment
  27. #      (http://www.zope.org/)."
  28. #    In the event that the product being advertised includes an
  29. #    intact Zope distribution (with copyright and license included)
  30. #    then this clause is waived.
  31. # 5. Names associated with Zope or Digital Creations must not be used to
  32. #    endorse or promote products derived from this software without
  33. #    prior written permission from Digital Creations.
  34. # 6. Modified redistributions of any form whatsoever must retain
  35. #    the following acknowledgment:
  36. #      "This product includes software developed by Digital Creations
  37. #      for use in the Z Object Publishing Environment
  38. #      (http://www.zope.org/)."
  39. #    Intact (re-)distributions of any official Zope release do not
  40. #    require an external acknowledgement.
  41. # 7. Modifications are encouraged but must be packaged separately as
  42. #    patches to official Zope releases.  Distributions that do not
  43. #    clearly separate the patches from the original work must be clearly
  44. #    labeled as unofficial distributions.  Modifications which do not
  45. #    carry the name Zope may be packaged in any form, as long as they
  46. #    conform to all of the clauses above.
  47. # Disclaimer
  48. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  49. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  50. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  51. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  52. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  53. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  54. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  55. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  56. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  57. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  58. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  59. #   SUCH DAMAGE.
  60. # This software consists of contributions made by Digital Creations and
  61. # many individuals on behalf of Digital Creations.  Specific
  62. # attributions are listed in the accompanying credits file.
  63. ##############################################################################
  64.  
  65. """WebDAV xml request objects."""
  66.  
  67. __version__='$Revision: 1.8 $'[11:-2]
  68.  
  69. import sys, os, string, regex
  70. from common import absattr, aq_base, urlfix, urlbase
  71. from OFS.PropertySheets import DAVProperties
  72. from xmltools import XmlParser
  73. from cStringIO import StringIO
  74. from urllib import quote
  75.  
  76.  
  77. class DAVProps(DAVProperties):
  78.     """Emulate required DAV properties for objects which do
  79.        not themselves support properties. This is mainly so
  80.        that non-PropertyManagers can appear to support DAV
  81.        PROPFIND requests."""
  82.     def __init__(self, obj):
  83.         self.__obj__=obj
  84.     def v_self(self):
  85.         return self.__obj__
  86.     p_self=v_self
  87.  
  88.  
  89.  
  90. class PropFind:
  91.     """Model a PROPFIND request."""
  92.     def __init__(self, request):
  93.         self.request=request
  94.         self.depth='infinity'
  95.         self.allprop=0
  96.         self.propname=0
  97.         self.propnames=[]
  98.         self.parse(request)
  99.         
  100.     def parse(self, request, dav='DAV:'):
  101.         self.depth=request.get_header('Depth', 'infinity')
  102.         if not (self.depth in ('0','1','infinity')):
  103.             raise 'Bad Request', 'Invalid Depth header.'
  104.         body=request.get('BODY', '')
  105.         self.allprop=(not len(body))
  106.         if not body: return
  107.         try:    root=XmlParser().parse(body)
  108.         except: raise 'Bad Request', sys.exc_info()[1]
  109.         e=root.elements('propfind', ns=dav)
  110.         if not e: raise 'Bad Request', 'Invalid xml request.'
  111.         e=e[0]
  112.         if e.elements('allprop', ns=dav):
  113.             self.allprop=1
  114.             return
  115.         if e.elements('propname', ns=dav):
  116.             self.propname=1
  117.             return
  118.         prop=e.elements('prop', ns=dav)
  119.         if not prop: raise 'Bad Request', 'Invalid xml request.'
  120.         prop=prop[0]
  121.         for val in prop.elements():
  122.             self.propnames.append((val.name(), val.namespace()))
  123.         if (not self.allprop) and (not self.propname) and \
  124.            (not self.propnames):
  125.             raise 'Bad Request', 'Invalid xml request.'
  126.         return
  127.  
  128.     def apply(self, obj, url=None, depth=0, result=None, top=1):
  129.         if result is None:
  130.             result=StringIO()
  131.             depth=self.depth
  132.             url=urlfix(self.request['URL'], 'PROPFIND')
  133.             url=urlbase(url)
  134.             result.write('<?xml version="1.0" encoding="utf-8"?>\n' \
  135.                          '<d:multistatus xmlns:d="DAV:">\n')
  136.         iscol=hasattr(obj, '__dav_collection__')
  137.         if iscol and url[-1] != '/': url=url+'/'
  138.         result.write('<d:response>\n<d:href>%s</d:href>\n' % quote(url))
  139.         if hasattr(aq_base(obj), 'propertysheets'):
  140.             propsets=obj.propertysheets.values()
  141.             obsheets=obj.propertysheets
  142.         else:
  143.             davprops=DAVProps(obj)
  144.             propsets=(davprops,)
  145.             obsheets={'DAV:': davprops}
  146.         if self.allprop:
  147.             stats=[]
  148.             for ps in propsets:
  149.                 if hasattr(aq_base(ps), 'dav__allprop'):
  150.                     stats.append(ps.dav__allprop())
  151.             stats=string.join(stats, '') or '<d:status>200 OK</d:status>\n'
  152.             result.write(stats)            
  153.         elif self.propname:
  154.             stats=[]
  155.             for ps in propsets:
  156.                 if hasattr(aq_base(ps), 'dav__propnames'):
  157.                     stats.append(ps.dav__propnames())
  158.             stats=string.join(stats, '') or '<d:status>200 OK</d:status>\n'
  159.             result.write(stats)
  160.         elif self.propnames:
  161.             rdict={}
  162.             for name, ns in self.propnames:
  163.                 ps=obsheets.get(ns, None)
  164.                 if ps is not None and hasattr(aq_base(ps), 'dav__propstat'):
  165.                     stat=ps.dav__propstat(name, rdict)
  166.                 else:
  167.                     prop='<n:%s xmlns:n="%s"/>' % (name, ns)
  168.                     code='404 Not Found'
  169.                     if not rdict.has_key(code):
  170.                         rdict[code]=[prop]
  171.                     else: rdict[code].append(prop)
  172.             keys=rdict.keys()
  173.             keys.sort()
  174.             for key in keys:
  175.                 result.write('<d:propstat>\n' \
  176.                              '  <d:prop>\n' \
  177.                              )
  178.                 map(result.write, rdict[key])
  179.                 result.write('  </d:prop>\n' \
  180.                              '  <d:status>HTTP/1.1 %s</d:status>\n' \
  181.                              '</d:propstat>\n' % key
  182.                              )
  183.         else: raise 'Bad Request', 'Invalid request'
  184.         result.write('</d:response>\n')        
  185.         if depth in ('1', 'infinity') and iscol:
  186.             for ob in obj.objectValues():
  187.                 dflag=hasattr(ob, '_p_changed') and (ob._p_changed == None)
  188.                 if hasattr(ob, '__dav_resource__'):
  189.                     uri=os.path.join(url, absattr(ob.id))
  190.                     depth=depth=='infinity' and depth or 0
  191.                     self.apply(ob, uri, depth, result, top=0)
  192.                     if dflag: ob._p_deactivate()
  193.         if not top: return result
  194.         result.write('</d:multistatus>')
  195.         return result.getvalue()
  196.  
  197.  
  198.  
  199. class PropPatch:
  200.     """Model a PROPPATCH request."""
  201.     def __init__(self, request):
  202.         self.request=request
  203.         self.values=[]
  204.         self.parse(request)
  205.  
  206.     def parse(self, request, dav='DAV:'):
  207.         body=request.get('BODY', '')
  208.         try:    root=XmlParser().parse(body)
  209.         except: raise 'Bad Request', sys.exc_info()[1]
  210.         vals=self.values
  211.         e=root.elements('propertyupdate', ns=dav)
  212.         if not e: raise 'Bad Request', 'Invalid xml request.'
  213.         e=e[0]
  214.         for ob in e.elements():
  215.             if ob.name()=='set' and ob.namespace()==dav:
  216.                 proptag=ob.elements('prop', ns=dav)
  217.                 if not proptag: raise 'Bad Request', 'Invalid xml request.'
  218.                 proptag=proptag[0]
  219.                 for prop in proptag.elements():
  220.                     # We have to ensure that all tag attrs (including
  221.                     # an xmlns attr for all xml namespaces used by the
  222.                     # element and its children) are saved, per rfc2518.
  223.                     name, ns=prop.name(), prop.namespace()
  224.                     e, attrs=prop.elements(), prop.attrs()
  225.                     if (not e) and (not attrs):
  226.                         # simple property
  227.                         item=(name, ns, prop.strval(), {})
  228.                         vals.append(item)
  229.                     else:
  230.                         # xml property
  231.                         attrs={}
  232.                         prop.remap({ns:'n'})
  233.                         prop.del_attr('xmlns:n')
  234.                         for attr in prop.attrs():
  235.                             attrs[attr.qname()]=attr.value()
  236.                         md={'__xml_attrs__':attrs}
  237.                         item=(name, ns, prop.strval(), md)
  238.                         vals.append(item)
  239.             if ob.name()=='remove' and ob.namespace()==dav:
  240.                 proptag=ob.elements('prop', ns=dav)
  241.                 if not proptag: raise 'Bad Request', 'Invalid xml request.'
  242.                 proptag=proptag[0]
  243.                 for prop in proptag.elements():
  244.                     item=(prop.name(), prop.namespace())
  245.                     vals.append(item)
  246.  
  247.     def apply(self, obj):
  248.         url=urlfix(self.request['URL'], 'PROPPATCH')
  249.         if hasattr(obj, '__dav_collection__'):
  250.             url=url+'/'
  251.         result=StringIO()
  252.         errors=[]
  253.         result.write('<?xml version="1.0" encoding="utf-8"?>\n' \
  254.                      '<d:multistatus xmlns:d="DAV:">\n' \
  255.                      '<d:response>\n' \
  256.                      '<d:href>%s</d:href>\n' % quote(url))
  257.         propsets=obj.propertysheets
  258.         for value in self.values:
  259.             status='200 OK'
  260.             if len(value) > 2:
  261.                 name, ns, val, md=value
  262.                 propset=propsets.get(ns, None)
  263.                 if propset is None:
  264.                     propsets.manage_addPropertySheet('', ns)
  265.                     propset=propsets.get(ns)
  266.                 propdict=propset._propdict()
  267.                 if propset.hasProperty(name):
  268.                     try: propset._updateProperty(name, val, meta=md)
  269.                     except:
  270.                         errors.append(str(sys.exc_info()[1]))
  271.                         status='409 Conflict'
  272.                 else:
  273.                     try: propset._setProperty(name, val, meta=md)
  274.                     except:
  275.                         errors.append(str(sys.exc_info()[1]))
  276.                         status='409 Conflict'
  277.             else:
  278.                 name, ns=value
  279.                 propset=propsets.get(ns, None)
  280.                 if propset is None or not propset.hasProperty(name):
  281.                     errors.append('Property not found: %s' % name)
  282.                     status='404 Not Found'
  283.                 else:
  284.                     try: propset._delProperty(name)
  285.                     except:
  286.                         errors.append('%s cannot be deleted.' % name)
  287.                         status='409 Conflict'
  288.             if result != '200 OK': abort=1
  289.             result.write('<d:propstat xmlns:n="%s">\n' \
  290.                          '  <d:prop>\n' \
  291.                          '  <n:%s/>\n' \
  292.                          '  </d:prop>\n' \
  293.                          '  <d:status>HTTP/1.1 %s</d:status>\n' \
  294.                          '</d:propstat>\n' % (ns, name, status))
  295.         errmsg=string.join(errors, '\n') or 'The operation succeded.'
  296.         result.write('<d:responsedescription>\n' \
  297.                      '%s\n' \
  298.                      '</d:responsedescription>\n' \
  299.                      '</d:response>\n' \
  300.                      '</d:multistatus>' % errmsg)
  301.         result=result.getvalue()
  302.         if not errors: return result
  303.         # This is lame, but I cant find a way to keep ZPublisher
  304.         # from sticking a traceback into my xml response :(
  305.         get_transaction().abort()
  306.         result=string.replace(result, '200 OK', '424 Failed Dependency')
  307.         return result
  308.  
  309.  
  310.  
  311.  
  312.  
  313. class Lock:
  314.     """Model a LOCK request."""
  315.     def __init__(self, request):
  316.         self.request=request
  317.         data=request.get('BODY', '')
  318.         self.scope='exclusive'
  319.         self.type='write'
  320.         self.owner=''
  321.         self.parse(data)
  322.  
  323.     def parse(self, data, dav='DAV:'):
  324.         root=XmlParser().parse(data)
  325.         info=root.elements('lockinfo', ns=dav)[0]
  326.         ls=info.elements('lockscope', ns=dav)[0]
  327.         self.scope=ls.elements()[0].name()
  328.         lt=info.elements('locktype', ns=dav)[0]
  329.         self.type=lt.elements()[0].name()
  330.         lo=info.elements('owner', ns=dav)
  331.         if lo: self.owner=lo[0].toxml()
  332.  
  333.  
  334.  
  335.  
  336.